--[[
   SVFFI serial protocol for generator support
   See http://www.svffi.com/en/
--]]

---@diagnostic disable: param-type-mismatch
---@diagnostic disable: missing-parameter

local PARAM_TABLE_KEY = 42
local PARAM_TABLE_PREFIX = "EFI_SVF_"

local MAV_SEVERITY = {EMERGENCY=0, ALERT=1, CRITICAL=2, ERROR=3, WARNING=4, NOTICE=5, INFO=6, DEBUG=7}

-- bind a parameter to a variable given
local function bind_param(name)
    local p = Parameter()
    assert(p:init(name), string.format('could not find %s parameter', name))
    return p
end

-- add a parameter and bind it to a variable
local function bind_add_param(name, idx, default_value)
    assert(param:add_param(PARAM_TABLE_KEY, idx, name, default_value), string.format('could not add param %s', name))
    return bind_param(PARAM_TABLE_PREFIX .. name)
end

-- setup script specific parameters
assert(param:add_table(PARAM_TABLE_KEY, PARAM_TABLE_PREFIX, 8), 'could not add param table')

--[[
  // @Param: EFI_SVF_ENABLE
  // @DisplayName: Generator SVFFI enable
  // @Description: Enable SVFFI generator support
  // @Values: 0:Disabled,1:Enabled
  // @User: Standard
--]]
EFI_SVF_ENABLE = bind_add_param("ENABLE", 1, 0)

--[[
  // @Param: EFI_SVF_ARMCHECK
  // @DisplayName: Generator SVFFI arming check
  // @Description: Check for Generator ARM state before arming
  // @Values: 0:Disabled,1:Enabled
  // @User: Standard
--]]
EFI_SVF_ARMCHECK = bind_add_param("ARMCHECK", 2, 1)

if EFI_SVF_ENABLE:get() ~= 1 then
   return
end

local auth_id = arming:get_aux_auth_id()
arming:set_aux_auth_failed(auth_id, "GEN: not in ARM state")


local uart = serial:find_serial(0) -- first scripting serial
if not uart then
   gcs:send_text(MAV_SEVERITY.ERROR, "GEN_SVF: unable to find serial port")
   return
end
uart:begin(115200)

local efi_backend = efi:get_backend(0)
if not efi_backend then
   gcs:send_text(MAV_SEVERITY.ERROR, "GEN_SVF: unable to find EFI backend")
   return
end

local function read_bytes(n)
   local ret = ""
   for _ = 1, n do
      ret = ret .. string.char(uart:read())
   end
   return ret
end

local auchCRCHi = {
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40
}

local auchCRCLo = {
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
    0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
    0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
    0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
    0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
    0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
    0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
    0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
    0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
    0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
    0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
    0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
    0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
    0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
    0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
    0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
    0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
    0x41, 0x81, 0x80, 0x40
}

--[[
   calculate crc16
--]]
local function get_crc16(s)
   local uchCRCHi = 0xFF
   local uchCRCLo = 0xFF
   for i = 1, #s do
      local b = string.byte(string.sub(s, i, i))
      local uIndex = uchCRCLo ~ b
      --gcs:send_text(MAV_SEVERITY.INFO, string.format("uIndex=%u", uIndex))
      uchCRCLo = uchCRCHi ~ auchCRCHi[uIndex+1]
      uchCRCHi = auchCRCLo[uIndex+1]
   end
   return (uchCRCHi << 8 | uchCRCLo)
end

local state = {}
state.last_read_us = uint32_t(0)
state.last_status = -1


--[[
   check for input and parse data
--]]
local function check_input()
   local n_bytes = uart:available():toint()
   --gcs:send_text(MAV_SEVERITY.INFO, string.format("n_bytes=%u %.2f", n_bytes, millis():tofloat()*0.001))
   if n_bytes < 31 then
      return
   end

   local s = read_bytes(n_bytes)
   local prefix, len = string.unpack("<HB", s, 1)
   if prefix ~= 0xa55a then
      gcs:send_text(MAV_SEVERITY.INFO, string.format("bad prefix 0x%x", prefix))
      return
   end
   if len+5 ~= n_bytes then
      gcs:send_text(MAV_SEVERITY.INFO, string.format("bad len %u %u", n_bytes, len))
      return
   end
   local crc = string.unpack("<H", s, 4+len)
   local s2 = string.sub(s,1,n_bytes-2)
   --gcs:send_text(MAV_SEVERITY.INFO, string.format("s2 n_bytes=%u len1=%u len2=%u", n_bytes, #s, #s2))
   local crc2 = get_crc16(s2)
   if crc ~= crc2 then
      gcs:send_text(MAV_SEVERITY.INFO, string.format("bad crc %x %x", crc, crc2))
      return
   end

   state.version, state.rpm, state.throttle = string.unpack("<BHH", string.sub(s,4,8))
   state.voltage, state.current, state.runtime = string.unpack("<HHI", string.sub(s,9,16))
   state.maint_time, state.lock_time, state.status = string.unpack("<HHB", string.sub(s,17,21))
   state.alarm, state.fuellevel, state.cht1 = string.unpack("<HBH", string.sub(s,22,26))
   state.cht2, state.pcb_temp = string.unpack("<HB", string.sub(s,27,29))

   if state.status ~= state.last_status then
      state.last_status = state.status
      local states = {"STOP", "IDLE", "RUN", "3", "CHARGE" }
      local name = states[state.status+1]
      if not name then
         name = string.format("Unknown%u", state.status)
      end
      gcs:send_text(MAV_SEVERITY.WARNING, string.format("Generator state: %s", name))
      if name ~= "RUN" and EFI_SVF_ARMCHECK:get() == 1 then
         arming:set_aux_auth_failed(auth_id, string.format("GEN: not in ARM state (%s)", name))
      else
         arming:set_aux_auth_passed(auth_id)
      end

   end

   state.last_read_us = micros()
end

--[[
   update EFI state
--]]
local function update_EFI()
   if state.last_read_us == uint32_t(0) then
      return
   end
   local cylinder_state = Cylinder_Status()
   local efi_state = EFI_State()
   local C_TO_KELVIN = 273.2

   cylinder_state:cylinder_head_temperature(state.cht1+C_TO_KELVIN)
   efi_state:engine_speed_rpm(state.rpm)

   efi_state:throttle_position_percent(state.throttle)
   efi_state:ignition_voltage(state.voltage*0.1)

   efi_state:cylinder_status(cylinder_state)
   efi_state:last_updated_ms(millis())

   -- Set the EFI_State into the EFI scripting driver
   efi_backend:handle_scripting(efi_state)

   gcs:send_named_float('GEN_VOLT', state.voltage*0.1)
   gcs:send_named_float('GEN_AMPS', state.current*0.1)
   gcs:send_named_float('GEN_STAT', state.status)
   logger.write('SVF','Curr,Volt,Status', 'ffB',
                state.current*0.1,
                state.voltage*0.1,
                state.status)


end


--[[
   main update function
--]]
local function update()
   check_input()
   update_EFI()

   return update, 100
end

gcs:send_text(MAV_SEVERITY.INFO, "GEN_SVF: loaded")

return update()
